﻿using System;
using System.Net.Sockets;
using System.Net;
using System.Collections.Generic;
using System.Threading;
using System.Text;

namespace ROUDP
{
	/*
	 * 使用样例
	 * 客户端和服务器建立了UDP 连接关系 握手
	 * 
	 * 现有的UDP连接
	 * 接着报文通过KCP  KCP下层通过UDP传输
	 * UDP 异常向上抛出给KCP
	 * KCP异常抛出给应用层
	 * 构建一个新的UDP连接用于这个通道数据传输
	 * 
	 * 
	 * 应用 
	 * KCP
	 * UDP
	 * 
	 * Connect
	 * 
	 * 
	 * var myUd = new MyUDPSocket();
	 * 
	 * 
	 * new KCP();
	 * kcp.outFunc = myU
	 */


	//每个层都有自己的队列
	//每个Agent 只管理
	//UDP 异常向上抛出
	//或者阻塞线程执行
	//应用层负责Timeout断开
	public class MyUDPAgent
	{
		private UdpClient udpClient;
		private Thread sendThread;
		private Thread recvThread;

		private ManualResetEvent signal = new ManualResetEvent(false);
		private Queue<Packet> packets = new Queue<Packet>();

		public IPEndPoint targetPoint;
		private Queue<byte[]> recvDatas = new Queue<byte[]>();

		public MyUDPAgent(int port)
		{
			udpClient = new UdpClient(port);
			sendThread = new Thread(SendTh);
			sendThread.Start();
			recvThread = new Thread(RecvTh);
			recvThread.Start();
		}
		private void SendTh()
		{
			while (true)
			{
				signal.WaitOne();
				Packet pack = null;
				lock (packets)
				{
					if (packets.Count > 0)
					{
						pack = packets.Dequeue();
					}
				}
				if (pack != null)
				{
					try
					{
						udpClient.Send(pack.fullData, pack.fullData.Length, targetPoint);
					}
					catch (Exception exp)
					{
						Console.WriteLine(exp.ToString());
					}
				}
				else {
					signal.Reset();
				}
			}
		}
		private void RecvTh()
		{

			var udpPort = new IPEndPoint(IPAddress.Any, 0);
			while (true)
			{
				try
				{
					var bytes = udpClient.Receive(ref udpPort);
					lock (recvDatas)
					{
						recvDatas.Enqueue(bytes);
					}
				}
				catch (Exception exp)
				{
					Console.WriteLine(exp.ToString());
				}
			}
		}
		public byte[] RecvData()
		{
			byte[] pack = null;
			lock (recvDatas)
			{
				if (recvDatas.Count > 0)
				{
					pack = recvDatas.Dequeue();
				}
			}
			return pack;
		}
		public void Send(Packet packet)
		{
			lock (packets)
			{
				packets.Enqueue(packet);
			}
			signal.Set();
		}
	}

	public enum PacketCMD
	{
		CMD_PUSH,
		CMD_WAITACK,
		CMD_ACKED,
	}

	//isACK seqId data
	public class Packet
	{
		public byte[] data;
		public PacketCMD cmd;
		public UInt32 sn;
		public byte[] fullData;
		public bool acked = false;
		public bool isAck = false;
		public int ackTimeout = 1;

		public void EncodeFull()
		{
			if (data != null)
			{
				fullData = new byte[data.Length + 4 + 1];
				if (isAck)
				{
					fullData[0] = 1;
				}
				else {
					fullData[0] = 0;
				}

				var bd = BitConverter.GetBytes(sn);
				Array.Copy(bd, 0, fullData, 1, 4);
				Array.Copy(data, 0, fullData, 5, data.Length);
			}
			else {
				fullData = new byte[0 + 4 + 1];
				if (isAck)
				{
					fullData[0] = 1;
				}
				else {
					fullData[0] = 0;
				}

				var bd = BitConverter.GetBytes(sn);
				Array.Copy(bd, 0, fullData, 1, 4);
			}
		}

		public void DecodeData(byte[] rcData, int len)
		{
			isAck = rcData[0] == 1;
			fullData = rcData;
			sn = BitConverter.ToUInt32(rcData, 1);
			data = new byte[len - 5];
			Array.Copy(rcData, 5, data, 0, len - 5);
		}
	}

	public class LoopList
	{
		private UInt32 start = 0;
		private UInt32 readPos = 0;
		private List<Packet> recvSlots = new List<Packet>();
		private UInt32 total;
		public LoopList(UInt32 n)
		{
			total = n;
			for (var i = 0; i < n; i++)
			{
				recvSlots.Add(null);
			}
		}


		//将新来的报文放到某个slot上面
		public void AddPacket(Packet pack, UInt32 index)
		{
			var id = (start + index) % (total);
			recvSlots[(int)id] = pack;
		}

		//尝试从Slot中获取头部的报文
		public Packet TakePacket()
		{
			int cp = (int)readPos;
			if (recvSlots[cp] == null)
			{
				return null;
			}
			readPos++;
			readPos %= total;
			return recvSlots[cp];
		}

		//移动start的标位置
		public void MoveStart()
		{
			recvSlots[(int)start] = null;
			start++;
			start %= total;
		}
	}

	public class KCP
	{
		private UInt32 sendId = 0;

		private UInt32 windSz = 10;
		private UInt32 winStart = 0;
		private UInt32 winEnd = 3;

		//每次滑动2 但是总长度4
		private UInt32 ackWinStart = 0;
		private UInt32 ackWinEnd = 6;


		//10ms
		public double interval = 0.05;
		private double accTime = 0;
		private UInt32 maxRecvNum = 999;


		//未发送过
		private Queue<Packet> sendQueue = new Queue<Packet>();
		private Queue<Packet> recvQueue = new Queue<Packet>();


		//已经发送过
		private LinkedList<Packet> sendBuf = new LinkedList<Packet>();
		private Queue<Packet> recvBuf = new Queue<Packet>();

		private HashSet<UInt32> ackYet = new HashSet<uint>();

		private LoopList recvList;
		//类似于UDPClient   TCPClient  connect  send recv
		//发送报文增加额外的头部 确定 序号和 ACK
		//创建
		public KCP()
		{
			winStart = 0;
			winEnd = windSz;
			ackWinStart = 0;
			ackWinEnd = windSz * 2;

			recvList = new LoopList(windSz * 2);
		}


		//User to KCP
		public void Send(byte[] data)
		{
			var pack = new Packet()
			{
				data = data,
			};
			sendQueue.Enqueue(pack);
		}

		//KCP to User
		public byte[] Recv()
		{
			byte[] data = null;
			if (recvBuf.Count > 0)
			{
				var pack = recvBuf.Dequeue();
				data = pack.data;
			}
			return data;
		}

		//UDP to KCP
		public void Input(byte[] data, int length)
		{
			var pack = new Packet();
			pack.DecodeData(data, length);
			lock (recvQueue)
			{
				recvQueue.Enqueue(pack);
			}
		}

		//KCP to UDP
		public System.Action<Packet> outputFunc;


		//超时重传等机制更新 
		public void Update(double delta)
		{
			accTime += delta;
			if (accTime >= interval)
			{
				accTime -= interval;
				HandleRecv();
				HandleAcked();
				HandleSend();
			}
		}

		private void PutPackInWindow()
		{
			//可以发送新的报文的条件窗口可以滑动了
			//窗口大小2 当sendId >= 0 < 2 的时候可以发送
			if (sendBuf.Count < windSz)
			{
				var newSn = sendId;
				var inWin = CheckInWin(newSn, winStart, winEnd);

				if (inWin)
				{
					if (sendQueue.Count > 0)
					{
						var seg = sendQueue.Dequeue();
						sendBuf.AddLast(seg);
						seg.cmd = PacketCMD.CMD_PUSH;
						seg.ackTimeout = 1;
						seg.sn = sendId++;
					}
				}
			}
		}

		private void SendWin()
		{

			//调用底层发送
			if (sendBuf.Count > 0)
			{
				for (var it = sendBuf.First; it != null;)
				{
					var seg = it.Value;
					var next = it.Next;
					//尚未发送
					if (seg.cmd == PacketCMD.CMD_PUSH)
					{
						seg.cmd = PacketCMD.CMD_WAITACK;
						seg.EncodeFull();
						outputFunc(seg);
					}
					else if (seg.cmd == PacketCMD.CMD_WAITACK)
					{
						if (seg.ackTimeout <= 0)
						{
							seg.ackTimeout = 1;
							outputFunc(seg);
						}
						else {
							seg.ackTimeout--;
						}
					}
					it = next;
				}
			}
		}

		private void HandleAcked()
		{
			if (sendBuf.Count > 0)
			{
				for (var it = sendBuf.First; it != null;)
				{
					var seg = it.Value;
					var next = it.Next;
					if (seg.cmd == PacketCMD.CMD_ACKED)
					{
						if (it == sendBuf.First)
						{
							winStart++;
							winEnd++;
							Console.WriteLine("SendWinSZ:" + winStart + ":" + winEnd);
						}
						sendBuf.Remove(it);
					}
					it = next;
				}
			}
		}

		private void HandleSend()
		{
			PutPackInWindow();
			SendWin();
		}

		private bool CheckInWin(UInt32 id, UInt32 st, UInt32 end)
		{
			var inWin = false;
			if (st > end)
			{
				inWin = id >= st || id < end;
			}
			else {
				inWin = id >= st && id < end;
			}
			return inWin;
		}

		private void CheckLoopList()
		{
			while (true)
			{
				var pack = recvList.TakePacket();
				if (pack != null)
				{
					recvBuf.Enqueue(pack);
				}
				else {
					break;
				}
			}
		}
		private void HandleRecv()
		{
			var c = 0;
			while (c <= maxRecvNum)
			{
				Packet recPack = null;
				lock (recvQueue)
				{
					//服务器接收到客户端的ACK
					if (recvQueue.Count > 0)
					{
						recPack = recvQueue.Dequeue();
					}
				}

				if (recPack != null)
				{
					c++;
					var seg = recPack;
					if (seg.isAck)
					{
						foreach (var s in sendBuf)
						{
							if (s.sn == seg.sn)
							{
								s.cmd = PacketCMD.CMD_ACKED;
								break;
							}
						}
					}
					else {//只发送一次ACK给服务器 失败则服务器自己报文重发
						  //服务器发送的新的报文 只接受服务器区间内的报文
						  //拒绝服务器重复的报文winStart = 0 winEnd = 2
						  //如果处理过了则不再处理
						  //服务器和每个客户端有独立的连接和独立的序号顺序
						  //连接建立的时刻就是 0 2 确定了
						var srvSn = seg.sn;
						var ret = CheckInWin(srvSn, ackWinStart, ackWinEnd);

						//窗口边界的怎么处理不知道是否已经验证过了两倍大小的接受窗口
						//sendSize 2  ackSz = 4
						if (ret)
						{
							var sendACK = new Packet();
							sendACK.isAck = true;
							sendACK.sn = seg.sn;
							sendACK.EncodeFull();
							//sendQueue.Enqueue(sendACK);
							outputFunc(sendACK);//立即发送ACK

							var tailHalfST = ackWinStart + windSz;
							var tailHalfEND = ackWinEnd + windSz;
							//数据出现在后半端，则可以移动接受窗口了
							if (CheckInWin(srvSn, tailHalfST, tailHalfEND))
							{
								ackWinStart++;
								ackWinEnd++;
								ackYet.Remove(ackWinStart - 1);
								recvList.MoveStart();
								Console.WriteLine("WinSz:" + ackWinStart + ":" + ackWinEnd);
							}

							if (!ackYet.Contains(srvSn))
							{
								ackYet.Add(srvSn);
								//recvBuf.Enqueue(seg);

								recvList.AddPacket(seg, srvSn - ackWinStart);
								CheckLoopList();
								Console.WriteLine("Ack:" + srvSn);
							}
						}
					}
				}
				else {
					break;
				}
			}

		}
	}
}
